第 5 章:在容器與主機中管理資料
資料卷 (Data volumes)
資料卷是一個可供一個或多個容器使用的特殊目錄,它繞過 UFS,可以提供很多有用的特性
- 資料卷可以在容器之間共享和重用
- 對資料卷的修改會立即生效
- 對資料卷的更新,不會影響映像檔
- 卷會一直存在,直到沒有容器使用
- 資料卷的使用,類似於 Linux 下對目錄或檔案進行 mount。
建立一個資料卷
在用 docker run
命令的時候,使用 -v
標記來建立一個資料卷並掛載到容器裡。在一次 run 中多次使用可以掛載多個資料卷。 下面建立一個 web 容器,並載入一個資料卷到容器的 /webapp
目錄。
sudo docker run -d -P --name web -v /webapp training/webapp python app.py
- 注意:也可以在 Dockerfile 中使用
VOLUME
來新增一個或者多個新的卷到由該映像檔建立的任意容器。
掛載一個主機目錄作為資料卷
使用 -v
標記也可以指定掛載一個本地主機的目錄到容器中去。
sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
上面的命令載入主機的 /src/webapp
目錄到容器的 /opt/webapp
目錄。這個功能在進行測試的時候十分方便,比如使用者可以放置一些程式到本地目錄中,來查看容器是否正常工作。本地目錄的路徑必須是絕對路徑,如果目錄不存在 Docker 會自動為你建立它。
- 注意:Dockerfile 中不支援這種用法,這是因為 Dockerfile 是為了移植和分享用的。然而,不同作業系統的路徑格式不一樣,所以目前還不能支援。
Docker 掛載資料卷的預設權限是讀寫,使用者也可以透過 :ro
指定為唯讀。
sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py
加了 :ro
之後,就掛載為唯讀了。
掛載一個本地主機檔案作為資料卷
v
標記也可以從主機掛載單個檔案到容器中。
sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
這樣就可以記錄在容器輸入過的命令了。
- 注意:如果直接掛載一個檔案,很多檔案編輯工具,包括
vi
或者sed --in-place
,可能會造成檔案 inode 的改變,從 Docker 1.1 .0起,這會導致報錯誤訊息。所以最簡單的辦法就直接掛載檔案的父目錄。
之前有說明過在執行 Docker Container 的時侯檔案系統會分為 Image 層、Init 層以及可讀可寫層這三個部份,當 Docker Container 刪除掉之後,存放在 Docker Container 上的資料也就會跟著刪除掉,因此需要把不想要被刪除掉的資料存放在實體機器上,避免資料不見的問題。
主要實作如何使用 Docker 的 Volume 功能,把資料寫入到實體機器上,主要的方式有二個方法:
- 在執行
docker run
指令時加上v
參數,使得 Container 裡面的檔案路徑Mapping 到實體主機的檔案路徑。 - 在撰寫 Dockerfile 時,加入
VOLUME
指令,做到把資料存放在實體的主機上。使用這種方法需要搭配docker inspect
指令,才能查詢到實體主機檔案的存放路徑在哪。(這個明 天會做介紹)
- 範例
在使用 docker run
指令時,指定 -v
參數,使得實體主機的資料夾路徑 Mapping 到 Container 的資料夾路徑,指令
docker run -it -v /home/user1/storage:/storage centos /bin/bash
/home/user1/storage
是實體主機的資料夾路徑 Mapping 到 Container 裡面的 /storage
資料夾路徑。
我們就會在上個畫面中的左邊視窗看到執行 docker run
指令時使用 -v
參數 Mapping 到實體主機的 /home/user1/storage
的資料夾。然後在右邊視窗中的實體主機 /home/user1/storage
的資料夾建立一個 helloworld.txt 的檔案,最後再回到左邊視窗的 Container 裡面的 /storage 資料夾去看 helloworld.txt 的檔案已經存在了。
- 範例
在使用 docker run
指令時,指定 -v
參數,但是沒有指定實體主機的檔案路徑,指令
docker run -it -v /storage centos /bin/bash
如果要找到在實體主機真實的資料夾路徑
docker inspect -f '{{.Mounts}}' 4c2a9ef663c2
4c2a9ef663c2
是 Container 的 ID
上圖就可以找到 Volume 在實體主機的真實路徑,這時侯就可以使用 cd
command 切進上圖查詢出來的資料夾路徑並且寫入一個檔案在此資料夾,但是要注意使用者權限的問題,這裡是直接切換成 root 權限
Example 1:如何讓 Container 之間的資料共享
撰寫一個 Dockerfile 使用 VOLUME
指令,把 Docker 的 Image Build 起來,然後啟動 Docker Container,把資料寫進在 Docker Container 裡面,最後使用 docker inspect
指令,找到 Mapping 到實體主機的資料夾路徑,確認是否有看到之前寫在 Container 裡面的檔案。
- Dockerfile
FROM centos
VOLUME ["/storage"]
另外在 VOLUME
指令的寫法可以寫成多個路徑
VOLUME ["/storage1", "/storage2", "/storage2"]
docker build -t volumetest .
輸入 docker build 指令時,資料夾要切換到和 Dockerfile 檔案同一層的資料夾路徑
docker images
啟動 Docker Container
docker run -it volumetest /bin/bash
因為 VOLUME 已經寫在 Dockerfile 裡,所以在使用 docker run
指令時沒有給 -v
參數
- 在 Container 裡面寫入一個檔案
- 在實體主機上使用
docker inspect
指令,找到 Volume 在實體主機的資料夾路徑
e2987aaab700
為 ContainerID,也可以使用指定 Container Name 的方式
- 使用 root 權限,切換到 Volume 的實體主機的資料夾路徑之後,可以看到在第4步驟在 Container 裡面寫的檔案,也可以在實體主機的資料夾看到
helloworld.txt
的檔案
Example 2:如何讓 Container 之間的資料共享
- 啟動第一個 Container 指令
docker run -it -v /data--name=container1 centos /bin/bash
- 啟動第二個 Container 指令
docker run -it --volumes-from container1 --name=container2 centos /bin/bash
-volumes-from
參數指定 Container Name 為 Container1 的 Volume 資料和Container2 做共享
- 測試二個 Container 之間資料是否能共享,畫面
左邊的 Container1 切換到 /data
資料夾之後建立一個 helloworld.txt 的檔案,之後在右邊的 Container2 切換到 /data
資料夾之後查看 helloworld.txt
的檔案內容為 HELLOWORLD
,這樣就代表了 Container 之間的資料有做到共享的效果。
今天介紹了 Dockerfile Volume 的用法以及 Container 之間的資料做共享,不用另外的架設 file server 就可以分享資料,使資料在使用上更加的方便,對於在備份資料方面也更加的簡單。
資料卷容器 (Data volume containers)
如果你有一些持續更新的資料需要在容器之間共享,最好建立資料卷容器。
資料卷容器,其實就是一個正常的容器,專門用來提供資料卷供其它容器掛載的。
首先,建立一個命名的資料卷容器 dbdata:
sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres
然後,在其他容器中使用 --volumes-from
來掛載 dbdata 容器中的資料卷。
sudo docker run -d --volumes-from dbdata --name db1 training/postgres
sudo docker run -d --volumes-from dbdata --name db2 training/postgres
還可以使用多個 --volumes-from
參數來從多個容器掛載多個資料卷。 也可以從其他已經掛載了容器卷的容器來掛載資料卷。
sudo docker run -d --name db3 --volumes-from db1 training/postgres
- 注意:使用
-volumes-from
參數所掛載資料卷的容器自己並不需要保持在執行狀態。
如果刪除了掛載的容器(包括 dbdata、db1 和 db2),資料卷並不會被自動刪除。如果要刪除一個資料卷,必須在刪除最後一個還掛載著它的容器時使用 docker rm -v
命令來指定同時刪除關聯的容器。 這可以讓使用者在容器之間升級和移動資料卷。具體的操作將在下一節中進行講解。
備份資料卷
首先使用 --volumes-from
標記來建立一個載入 dbdata 容器卷的容器,並 從本地主機掛載當前到容器的 /backup 目錄。命令以下:
sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
容器啟動後,使用了 tar
命令來將 dbdata 卷備份為本地的 /backup/backup.tar
。
恢復資料卷
如果要恢復資料到一個容器,首先建立一個帶有資料卷的容器 dbdata2。
sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
然後建立另一個容器,掛載 dbdata2 的容器,並使用 untar
解壓備份檔案到掛載的容器卷中。
sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf
/backup/backup.tar
使用 Docker 的 Volume 來部署 war 檔
我們在開發的過程中直接把 war 檔包進去 Docker Image 之後,如果發現程式需要修改,那改完之後就需要重新 Build Image 以及重新啟動 Container,這樣有點麻煩,如果能夠把放 war 檔的資料夾共享在實體主機上的資料夾,這樣只要把改完的 war 檔重新的放在實體主機上的資料夾,就可以不用重 Build Image 以及重新啟動 Container。
- Dockerfile
FROM java
MAINTAINER jack
RUN apt-get update && apt-get install -y wget
RUN cd /
RUN wget http://apache.stu.edu.tw/tomcat/tomcat-7/v7.0.82/bin/apache-tomcat-7.0.82.tar.gz
RUN tar zxvf apache-tomcat-7.0.82.tar.gz
VOLUME ["/apache-tomcat-7.0.82/webapps"]
CMD ["/apache-tomcat-7.0.82/bin/catalina.sh", "run"]這裡使用的 Base Image 修改成 java 這樣就不用下載 JDK 以及設定 JAVA_HOME 的環境變數,另外在 Dockerfile 使用 VOLUME 指令,把放 war 的資料夾可以分享在實體主機的資料夾裡。
- Build Docker Image
docker build -t tomcatserver .
這裡指定的 Image Name 為 tomcatserver
- 使用 Build 完成的 Docker Image,執行 Docker container
docker run -d --name tomcat -p 8080:8080 tomcatserver
- 使用
docker inspect
指令,找到實體資料夾的路徑
docker inspect -f '{{.Mounts}}' tomcat
- 查詢檔案路徑後就可以下載 Jenskin 的 war 檔,放到實體資料夾的路徑裡
- 使用 Browser 連到 URL http://10.1.3.243:8080/jenkins,IP 為實體主機的 IP
- 看到以上的畫面需要輸入 Jenkins 認證的密碼,所以需要執行
docker exec
指令把 Container 上的密碼印出來
docker exec tomcat cat /root/.jenkins/secrets/initialAdminPassword
docker exec
後面的 tomcat 是 Container Name,也可 以使用 ContainerID 的方式